package hk.edu.cityu.is.riskmgmt.algorithm.interbank;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

/**
 * @author Zhimin Hua
 * 
 */
public class BankNetworkClearing {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger
			.getLogger(BankNetworkClearing.class);

	public static void main(String[] args) {
		BasicConfigurator.configure();
		int bankNO = 3;
		double[] cashReserve = { 2, 3, 4 };
		String[] bankID = { "100", "101", "102" };
		double[] security = { 1, 0, 1 };
		double[][] inter = { { 0, 2, 2 }, { 0, 0, 4 }, { 0, 0, 0 } };
		// The following three arrays are used to store the results after the
		// clearing process
		double[] P_star = new double[bankNO];

		String[] defaultSequence = new String[bankNO];
		String[] nonDefaultBanks = new String[bankNO];
		double[] leftAsset = new double[bankNO];
		double[] leftCash = new double[bankNO];
		double[] leftSecurity = new double[bankNO];
		BankNetworkClearing b = new BankNetworkClearing();

		int a = b.doClearing(bankNO, bankID, cashReserve, security, inter,
				P_star, defaultSequence, nonDefaultBanks, leftAsset, leftCash,
				leftSecurity);
		if (logger.isInfoEnabled()) {
			logger.info("main(String[]) - Total number of default banks is: " + a); //$NON-NLS-1$
		}

		if (logger.isInfoEnabled()) {
			logger.info("main(String[]) - The clearing vector is: "); //$NON-NLS-1$
		}
		for (int i = 0; i < bankNO; i++) {
			if (logger.isInfoEnabled()) {
				logger.info("main(String[]) - " + P_star[i] + " "); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
		if (logger.isInfoEnabled()) {
			logger.info("main(String[])"); //$NON-NLS-1$
		}

		if (logger.isInfoEnabled()) {
			logger.info("main(String[]) - The left asset vector after clearing is: "); //$NON-NLS-1$
		}
		for (int i = 0; i < bankNO; i++) {
			if (logger.isInfoEnabled()) {
				logger.info("main(String[]) - A:" + leftAsset[i] + " S" + leftSecurity[i] + " C" + leftCash[i]); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
		}
		if (logger.isInfoEnabled()) {
			logger.info("main(String[])"); //$NON-NLS-1$
		}

		if (logger.isInfoEnabled()) {
			logger.info("main(String[]) - The defaulting sequence is "); //$NON-NLS-1$
		}
		for (int i = 0; i < a; i++) {
			if (logger.isInfoEnabled()) {
				logger.info("main(String[]) - " + defaultSequence[i] + " "); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
		if (logger.isInfoEnabled()) {
			logger.info("main(String[])"); //$NON-NLS-1$
		}

	}

	/**
	 * 
	 * @param bankNO
	 * @param bankID
	 * @param cashReserve
	 * @param security
	 * @param interpaymentWeight
	 * @return clearingVector
	 * @return defaultSquence
	 * @return nonDefaultBanks
	 * @return leftAsset, considering the value of FPs
	 * @return leftSecurity, spend operating cashflow first.
	 * @return the number of default banks
	 */
	public int doClearing(int bankNO, String[] bankID, double[] cashReserve,
			double[] security, double[][] interpaymentWeight,
			double[] clearingVector, String[] defaultSquence,
			String[] nonDefaultBanks, double[] leftAsset, double[] leftCash,
			double[] leftSecurity) {
		int defaultNo = 0;

		double[] P_bar = new double[bankNO];// total payout for each bank
		for (int i = 0; i < bankNO; i++) {
			P_bar[i] = 0;
			for (int j = 0; j < bankNO; j++)
				P_bar[i] = P_bar[i] + interpaymentWeight[i][j];
		}

		double[][] PI = new double[bankNO][bankNO];// relative liabilities
													// matrix
		for (int i = 0; i < bankNO; i++)
			for (int j = 0; j < bankNO; j++)
				if (P_bar[i] != 0)
					PI[i][j] = interpaymentWeight[i][j] / P_bar[i];

		boolean[] defaultFlag = new boolean[bankNO];
		for (int i = 0; i < bankNO; i++)
			defaultFlag[i] = false;

		double[] P_star = new double[bankNO];// clearing vector
		for (int i = 0; i < bankNO; i++)
			P_star[i] = P_bar[i];

		boolean flag = true;
		while (flag) {
			// step 1. try to clear the system and see any default.
			flag = false;
			double[] netted = new double[bankNO];
			for (int i = 0; i < bankNO; i++) {
				double temp = 0;
				for (int j = 0; j < bankNO; j++)
					temp = temp + PI[j][i] * P_star[j];
				netted[i] = temp + cashReserve[i] + security[i] - P_star[i];
			}
			for (int i = 0; i < bankNO; i++) {
				if ((netted[i] < 0) && (!defaultFlag[i])) {
					defaultFlag[i] = true;
					defaultSquence[defaultNo++] = bankID[i];
					flag = true;
				}
			}
			// step 2. update P_star
			int[][] defaultMatrix = new int[bankNO][bankNO];
			int[][] nondefaultMatrix = new int[bankNO][bankNO];
			for (int i = 0; i < bankNO; i++) {
				if (defaultFlag[i])
					defaultMatrix[i][i] = 1;
				else
					nondefaultMatrix[i][i] = 1;
			}

			double[] temp = new double[bankNO];
			for (int j = 0; j < bankNO; j++) {
				for (int k = 0; k < bankNO; k++) {
					temp[j] = temp[j] + defaultMatrix[j][k] * P_star[k]
							+ nondefaultMatrix[j][k] * P_bar[k];
				}
			}
			double[] temp2 = new double[bankNO];

			for (int j = 0; j < bankNO; j++) {
				for (int k = 0; k < bankNO; k++) {
					temp2[j] = temp2[j] + PI[k][j] * temp[k];
				}
				temp2[j] = temp2[j] + cashReserve[j] + security[j];
			}

			for (int i = 0; i < bankNO; i++) {
				P_star[i] = 0;
				for (int k = 0; k < bankNO; k++) {
					P_star[i] = P_star[i] + defaultMatrix[i][k] * temp2[k]
							+ nondefaultMatrix[i][k] * P_bar[k];
				}
			}

		}// while loop

		// return the clearing vector, left asset vector after clearing process
		// and the default number.
		for (int i = 0; i < bankNO; i++)
			clearingVector[i] = P_star[i];

		for (int i = 0; i < bankNO; i++) {
			double temp = 0;
			for (int j = 0; j < bankNO; j++)
				temp = temp + PI[j][i] * P_star[j];
			leftAsset[i] = temp + cashReserve[i] + security[i] - P_star[i];
		}
		// return the leftCash and leftSecurity respectively
		for (int i = 0; i < bankNO; i++) {
			double payin = 0;// total pay_in
			for (int j = 0; j < bankNO; j++)
				payin = payin + PI[j][i] * P_star[j];
			if (defaultFlag[i]) {
				leftSecurity[i] = security[i] + payin + cashReserve[i]
						- P_bar[i];// should be negative
				leftCash[i] = 0;
			} else {
				if (payin + cashReserve[i] - P_star[i] >= 0) {
					leftSecurity[i] = security[i];
					leftCash[i] = payin + cashReserve[i] - P_star[i];
				} else {
					leftSecurity[i] = security[i] + payin + cashReserve[i]
							- P_star[i];
					leftCash[i] = 0;
				}
			}
			;
		}

		int counter = 0;
		for (int i = 0; i < bankNO; i++) {
			if (!defaultFlag[i]) {
				nonDefaultBanks[counter] = bankID[i];
				counter++;
			}
		}

		return defaultNo;
	}
}
